package cs6238.client;

import java.io.IOException;
import java.io.PrintStream;
import java.security.KeyStore;
import java.security.cert.X509Certificate;
import java.util.Scanner;
import java.util.UUID;

import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import cs6238.common.CertificateHelper;
import cs6238.common.Configuration;
import cs6238.common.DelegateEntry;
import cs6238.common.message.command.DelegateCommand;
import cs6238.common.message.command.GetFileCommand;
import cs6238.common.message.command.ListHostsCommand;
import cs6238.common.message.command.LsCommand;
import cs6238.common.message.command.PutFileCommand;

/**
 * Entry point of the client
 */
public class ClientMain {
	
	private ClientCommunication _comm;
	private Scanner _userInput;
	private PrintStream _out;
	private String _localHostname;
	
	public static void main(String args[]) throws Exception{
		ClientMain client = new ClientMain();
		client.run();
		System.out.println("Client exiting...");
	}
	
	public ClientMain() throws IOException{
		_out = System.out;
		_userInput = new Scanner(System.in);

	}

	private void run() throws Exception {
		Configuration clientConfig = Configuration.loadClientSettings();
		int serverPort = Integer.parseInt(clientConfig.getSetting(Configuration.NETWORK_PORT));
		String serverAddress = clientConfig.getSetting(Configuration.SERVER_ADDRESS);
		
		String keyStorePath = clientConfig.getSetting(Configuration.KEYSTORE_PATH);
		String keyStorePassword = clientConfig.getSetting(Configuration.KEYSTORE_PASSWORD);
		String trustStorePath = clientConfig.getSetting(Configuration.TRUSTSTORE_PATH);
		String trustStorePassword = clientConfig.getSetting(Configuration.TRUSTSTORE_PASSWORD);
		
		//System.setProperty("javax.net.debug", "all");
		System.setProperty("javax.net.ssl.keyStore", keyStorePath);
		System.setProperty("javax.net.ssl.keyStorePassword", keyStorePassword);
		System.setProperty("javax.net.ssl.trustStore", trustStorePath);
		System.setProperty("javax.net.ssl.trustStorePassword", trustStorePassword);
		
		SSLSocketFactory socketFactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
		SSLSocket socket = (SSLSocket)socketFactory.createSocket(serverAddress, serverPort);
		socket.startHandshake();
		
		KeyStore keys = CertificateHelper.readJKS(keyStorePath, keyStorePassword);
		X509Certificate cert = (X509Certificate)keys.getCertificate(CertificateHelper.ALIAS);
		_localHostname = CertificateHelper.getHostname(cert);
		_comm = new ClientCommunication(socket);
		
		while(true){
			String command = getConsoleInputCommand(">");
			executeCommand(command);
		}
	}
	
	protected void setComm(ClientCommunication comm){
		_comm = comm;
	}
	
	/**
	 * Parse string and execute command
	 * @param command
	 * @throws IOException 
	 */
	protected void executeCommand(String command) throws IOException{
		String[] commandParts = command.split(" ");
		
		if("quit".equals(command)){
			_comm.endConnection();
			System.exit(0);
		}
		else if("ls".equals(command) && commandParts.length == 1){
			LsCommand lsCommand = new LsCommand();
			_comm.executeCommand(lsCommand);
		}
		else if("get".equals(commandParts[0]) && commandParts.length==2){
			UUID fileId = _comm.getFileIdForFilename(commandParts[1]);
			if(fileId == null){
				_out.println("Server did not recognize filename");
				return;
			}
			
			GetFileCommand getCommand = new GetFileCommand();
			getCommand.setFileId(fileId);
			_comm.executeCommand(getCommand, commandParts[1]);
		}
		else if("put".equals(commandParts[0]) && commandParts.length == 2){
			PutFileCommand putCommand = new PutFileCommand();
			putCommand.setClientFilename(commandParts[1]);
			_comm.executeCommand(putCommand);
		}
		else if("delegate".equals(commandParts[0]) && commandParts.length == 5){
			DelegateCommand delegateCommand = new DelegateCommand();
			UUID clientFileId = _comm.getFileIdForFilename(commandParts[1]);
			if(clientFileId == null){
				_out.println("Server did not recognize filename");
				return;
			}
			
			String delegatedHost = commandParts[2];
			long endingTime = System.currentTimeMillis() + Long.parseLong(commandParts[3]) * 1000 * 60 ;
			boolean star = Boolean.parseBoolean(commandParts[4]);
			
			DelegateEntry entry = new DelegateEntry(clientFileId, _localHostname, delegatedHost, endingTime, star);
			delegateCommand.setDelegateEntry(entry);
			_comm.executeCommand(delegateCommand);
		}
		else if("listhosts".equals(commandParts[0]) && commandParts.length == 1){
			ListHostsCommand listHostsCommand = new ListHostsCommand();
			_comm.executeCommand(listHostsCommand);
		}
		else if("whoami".equals(commandParts[0]) && commandParts.length == 1){
			_out.println(this._localHostname);
		}
		else if("help".equals(commandParts[0])){
			_out.println("Command usage:");
			_out.println("ls");
			_out.println("get <filename>");
			_out.println("put <filename>");
			_out.println("delegate <filename> <hostname> <minutes> <isStar>");
			_out.println("listhosts");
			_out.println("whoami");
			_out.println("quit");
		}
		else{
			_out.println("Unknown command: " + command + ", try 'help'");
		}
	}
	
	/**
	 * Only used for unit tests
	 */
	protected void setLocalHostname(String localHostname){
		_localHostname = localHostname;
	}

	private String getConsoleInputCommand(String prompt) {
		_out.print(prompt);
		return _userInput.nextLine();
	}

	public void setOut(PrintStream out) {
		_out = out;
	}

}
